#include <GameEngine/Graphics/Camera/camera_manipulator_isometric.h>
#include <GameEngine/Geometry/Core/angle.h>
#include <GameEngine/Geometry/Surfaces/plane3d.h>

#include <iostream>


#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

#ifndef DOXYGEN_IGNORE
#include "camera_manipulator_isometric.moc"
#endif

//=================================================================================


//! Default constructor
CameraManipulatorIsometric::CameraManipulatorIsometric() :
	horizontal_rotation_(true),
	vertical_pan_(false),
	CameraManipulator() {}

//! Copy constructor
/*!
 @param rhs The camera manipulator to copy
 */
CameraManipulatorIsometric::CameraManipulatorIsometric(const CameraManipulatorIsometric& rhs) : 
	horizontal_rotation_(rhs.horizontal_rotation_),
	vertical_pan_(rhs.vertical_pan_),
	CameraManipulator(rhs) {}

//! Assignment operator
/*!
 @param rhs The camera manipulator to copy
 @return A reference to this camera manipulator
 */
CameraManipulatorIsometric& CameraManipulatorIsometric::operator=(const CameraManipulatorIsometric& rhs) {
	if ( &rhs != this ) {
		CameraManipulator::operator=(rhs);
		horizontal_rotation_ = rhs.horizontal_rotation_;
		vertical_pan_ = rhs.vertical_pan_;
	}
	return *this;
}

//! Destructor
CameraManipulatorIsometric::~CameraManipulatorIsometric() {}

//! Apply the given manipulation to the camera
/*!
 *  This is the specific implementation for an CameraManipulatorIsometric.
 *
 *  The CameraManipulatorIsometric allows to zoom, pan and rotate in a limited
 *  subset of angles. It is typically meant to explore isometric maps (although
 *  it is not limited to this purpose).
 *
 @param button The mouse button that was pressed
 @param mouse_move The total amout of displacement of the mouse on the screen
 @return True if given manipulation could be applied to the camera, false otherwise
 */
bool CameraManipulatorIsometric::manipulate(MouseButton button, const Vector2d<int>& mouse_move) {
	// Check whether mouse WAS moved
	if ( mouse_move.square_length() == 0 ) return false;

	// Scan all bindings
	bool res = false;
	for ( QList< QPair<MouseButton, CameraEffect> >::const_iterator it = bindings_.begin(); it != bindings_.end(); ++it ) {
		if ( it->first != button ) continue;

		// Camera should be zoomed
		if ( it->second == EffectZoom ) {
			// Get zoom movement
			float zoom = ( mouse_move.x() + mouse_move.y() ) * zoom_speed_;

			// Zoom camera
			camera_.zoom() += zoom;
			if ( camera_.zoom() > 0.95f ) camera_.zoom() = 0.95f;
			res = true;
			continue;
		}

		// Camera should be panned
		if ( it->second == EffectPan ) {
			// Get the normal orientations
			const Point3d<float>& eye = camera_.eye();
			const Point3d<float>& target = camera_.target();
			Vector3d<float> up = camera_.up();
			Vector3d<float> n = up.cross( Vector3d<float>(eye, target) ).normalize();
			Vector3d<float> n2 = up.cross( n ).normalize();

			// Get pan movement
			Vector3d<float> move = ( n * mouse_move.x() - n2 * mouse_move.y() ) * pan_speed_;

			// Pan camera
			camera_.eye() = Point3d<float>(eye.x()+move.x(),eye.y()+move.y(),eye.z() + ( vertical_pan_ ? move.z() : 0.0f ));
			camera_.target() = Point3d<float>(target.x()+move.x(),target.y()+move.y(),target.z() + ( vertical_pan_ ? move.z() : 0.0f ));
			res = true;
			continue;
		}

		// Camera should be rotated
		if ( it->second == EffectRotate ) {
			// Get vertical rotate movement
			bool transformed = false;

			// Get lateral rotate movement
			if ( horizontal_rotation_ == true ) {
				// Get the camera features
				const Point3d<double>& eye = camera_.eye();
				const Point3d<double>& target = camera_.target();

				// Get the circle orientations
				Plane3d<double> plane(eye, camera_.up());
				Point3d<double> center = plane.project(target);
				Vector3d<double> ref = Vector3d<double>(center,eye).normalize();
				Vector3d<double> n = camera_.up().cross(ref).normalize();

				// Compute new position of the observer
				Angle<double> angle(-mouse_move.x() * rotation_speed_, Angles::Deg);
				double cos_a = cos(angle.rad()) - cos(0.0), sin_a = sin(angle.rad()) - sin(0.0);
				Vector3d<double> move = ref * cos_a + n * sin_a;
				Point3d<double> new_eye = Point3d<double>( eye.x()+move.x(),eye.y()+move.y(),eye.z()+move.z() );

				// Correction of the distance (numeric inaccuracies)
				float coeff = center.get_distance(eye) / Vector3d<float>(center, new_eye).length();
				camera_.eye() = Vector3d<double>(Point3d<double>(0,0,0),center) + Vector3d<double>(center, new_eye)*coeff;
				transformed = true;
			}

			// Check if camera was transformed
			if ( transformed == false ) continue;
			res = true;
			continue;
		}
	}
	if ( res ) emit manipulated();
	return res;
}

//! Accessor to the horizontal rotation flag (read-write)
/*!
 @return A reference to the horizontal rotation flag
 @note If the flag is set to true, horizontal rotation is allowed
 */
bool& CameraManipulatorIsometric::horizontal_rotation() {
	return horizontal_rotation_;
}

//! Accessor to the horizontal rotation flag (read only)
/*!
 @return The horizontal rotation flag
 @note If the flag is set to true, horizontal rotation is allowed
 */
bool CameraManipulatorIsometric::horizontal_rotation() const {
	return horizontal_rotation_;
}

//! Accessor to the vertical pan flag (read-write)
/*!
 @return A reference to the vertical pan flag
 @note If the flag is set to true, vertical pan is allowed
 */
bool& CameraManipulatorIsometric::vertical_pan() {
	return vertical_pan_;
}

//! Accessor to the vertical pan flag (read only)
/*!
 @return The vertical pan flag
 @note If the flag is set to true, vertical pan is allowed
 */
bool CameraManipulatorIsometric::vertical_pan() const {
	return vertical_pan_;
}


#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif
